﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects.DataClasses;
using System.Data.Objects;
using System.Data;
using System.ServiceModel.DomainServices.EntityFramework;
using System.Linq.Expressions;

namespace ServerFramework.Data
{
    public class BaseRepository<TEntity> : IRepository<TEntity>
        where TEntity : EntityObject, new()
    {
        public BaseRepository(IDbContextManager manager)
        {
            this.SetContext(manager.GetDbContext());
        }

        private void SetContext(IDbContext ctx)
        {
            this.dbContext = ctx;
            this.ObjectContext = ctx.Context as ObjectContext;
            this.FetchEntitySet();
        }

        private IDbContext dbContext;

        protected ObjectContext ObjectContext { get; private set; }
        protected ObjectSet<TEntity> ObjectSet { get; private set; }

        //looks for an IQueryable<TEntity> property in the ObjectContext 
        //and gets its name to be used in other methods
        private void FetchEntitySet()
        {
            var entitySetProperty =
               this.ObjectContext.GetType().GetProperties()
                   .Single(p => p.PropertyType.IsGenericType && typeof(IObjectSet<TEntity>).IsAssignableFrom(p.PropertyType));

            this.ObjectSet = entitySetProperty.GetValue(this.ObjectContext, null) as ObjectSet<TEntity>;
        }

        //to be implemented by derived classes if needed
        protected virtual IQueryable<TEntity> BuildQuery(ObjectQuery<TEntity> query)
        {
            return query;
        }

        public virtual IQueryable<TEntity> GetQuery()
        {
            var baseQuery = this.ObjectSet as ObjectQuery<TEntity>;
            return this.BuildQuery(baseQuery);
        }

        public virtual void Add(TEntity entity)
        {
            this.ObjectSet.AddObject(entity);
        }

        public virtual void Update(TEntity entity)
        {
            if (entity.EntityKey == null)
                entity.EntityKey = this.ObjectContext.CreateEntityKey(this.ObjectSet.Name, entity);

            object existingEntity = null;
            this.ObjectContext.TryGetObjectByKey(entity.EntityKey, out existingEntity);
            if (existingEntity == null)
            {
                throw new System.Data.UpdateException("The entity being updated does not exist.");
            }

            this.ObjectSet.ApplyCurrentValues(entity);
        }

        public virtual void AttachAsModified(TEntity current, TEntity original = null)
        {
            if (original == null)
                this.ObjectSet.AttachAsModified(current);
            else
                this.ObjectSet.AttachAsModified(current, original);
        }

        public virtual void Delete(TEntity entity)
        {
            if (entity.EntityState == EntityState.Detached)
                this.ObjectSet.Attach(entity);

            this.ObjectSet.DeleteObject(entity);
        }

        public virtual TEntity GetEntity(int entityId)
        {
            return this.GetQuery().Where(this.GetFilterByIdExpression(entityId)).FirstOrDefault();
        }

        //generates a lambda like e=> e.IdProperty == entityId
        private Expression<Func<TEntity, bool>> GetFilterByIdExpression(int entityId)
        {
            var keyProperties = typeof(TEntity)
                                      .GetProperties()
                                      .Where(p => p.IsDefined(typeof(EdmScalarPropertyAttribute), false) &&
                                                    ((EdmScalarPropertyAttribute)p.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false)[0])
                                                        .EntityKeyProperty == true);

            //we assume there is only one Key
            var keyProperty = keyProperties.First();

            ParameterExpression paramExpr = Expression.Parameter(typeof(TEntity), "e");
            MemberExpression memberExpr = Expression.Property(paramExpr, keyProperty.Name);
            var keyCompareValue = entityId;
            Expression bodyExpr = Expression.Equal(memberExpr, Expression.Constant(keyCompareValue, keyProperty.PropertyType));

            return Expression.Lambda<Func<TEntity, bool>>(bodyExpr, paramExpr);
        }
    }
}
